/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package pt.mleiria.supervisedlearning;

import java.io.IOException;
import java.util.logging.Level;
import pt.mleiria.datastructures.iterations.IteratorProcessor;
import pt.mleiria.datastructures.matrixAlgebra.Matrix;
import pt.mleiria.utils.EnvSettings;
import pt.mleiria.utils.IOUtils;

/**
 *
 * @author manuel
 */
public class GradientDescent extends IteratorProcessor {
    
    private final String dataFile;
    private Matrix x;
    private Matrix y;
    private Matrix theta;
    private double alpha;
    private double[] J;
    private final boolean normalizeData;
    private final int numFeatures;
    /**
     * Sample Data
     */
    private int m = 0;

    /**
     * Todas as colunas excepto a última são features i.e. x A última coluna é o
     * valor do output i.i., y
     *
     * @param dataFile
     * @param normalizeData
     * @param numFeatures
     */
    public GradientDescent(final String dataFile, final boolean normalizeData, final int numFeatures) {
        this.dataFile = dataFile;
        this.normalizeData = normalizeData;
        this.numFeatures = numFeatures;
    }

    @Override
    public void initializeIterations() {
        try {
            final double[][] rawData = IOUtils.loadFileToComponents(dataFile, ",");
            loadValues(rawData);
            J = new double[getMaximumIterations() + 1];
            alpha = 0.01;
            setDesiredPrecision(0.00001);
            theta = new Matrix(numFeatures, 1);
            computeCost();
        } catch (IOException ex) {
            LOG.log(Level.SEVERE, null, ex);
        }
    }

    /**
     *
     * @param rawData
     */
    private void loadValues(double[][] rawData) {
        final int rows = rawData.length;
        final int columns = rawData[0].length - 1;

        double[][] xComponents = new double[rows][columns];
        double[][] yComponents = new double[rows][1];
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                xComponents[i][j] = rawData[i][j];
            }
            yComponents[i][0] = rawData[i][columns];
        }
        
        x = new Matrix(xComponents);
        if(normalizeData){
            x = x.featureNormalization();
        }
        x = new Matrix(x.toComponents()).addOnes();
        y = new Matrix(yComponents);
        m = y.rows();
    }

    @Override
    public double evaluateIteration() {
        final double[][] newTheta = new double[theta.rows()][theta.columns()];
        for (int j = 0; j < theta.rows(); j++) {
            double sumTmp = 0.0;
            for (int i = 0; i < m; i++) {
                double aux = 0.0;
                for (int k = 0; k < theta.rows(); k++) {
                    aux += theta.component(k, 0) * x.component(i, k);
                }
                sumTmp += (aux - y.component(i, 0)) * x.component(i, j);
            }
            newTheta[j][0] = theta.component(j, 0) - alpha * Math.pow(m, -1) * sumTmp;
        }
        theta = new Matrix(newTheta);
        computeCost();
        final double precision = J[getIterations() - 1] - J[getIterations()];
        return precision;
    }

    @Override
    public void finalizeIterations() {
        super.finalizeIterations();
        //LOG.info(IOUtils.showArrayContents(J));
    }
    
    private void computeCost() {
        double jLocal = 0.0;
        for (int i = 0; i < m; i++) {
            double aux = 0.0;
            for (int j = 0; j < theta.rows(); j++) {
                aux += theta.component(j, 0) * x.component(i, j);
            }
            jLocal += Math.pow(aux - y.component(i, 0), 2);
        }
        jLocal = Math.pow(2 * m, -1) * jLocal;
        J[getIterations()] = jLocal;
    }

    /**
     *
     * @return
     */
    public Matrix getX() {
        return x;
    }

    /**
     *
     * @return
     */
    public Matrix getTheta() {
        return theta;
    }

    /**
     *
     * @return
     */
    public Matrix getY() {
        return y;
    }

    public double[] getJ() {
        return J;
    }
    
    public static void main(String[] args){
        String ex1data2 = EnvSettings.EX1DATA2;
        GradientDescent gd = new GradientDescent(ex1data2, true, 3);
        gd.evaluate();
    }
}
